import * as THREE from '../../../../modules/three/three.module.js'
import { Text } from '../../../../modules/troika-three-text/troika-three-text.esm.js';
import {
  flatten,
  vector3sToFloat32BufferAttribute,
} from '../util/StaticMethods.js';
import {assets3dBaseUrl} from "../CommonConsts.js";

const _modelViewMatrix = new THREE.Matrix4();
const _mvPosition = new THREE.Vector3();
const _tmpMat = new THREE.Matrix4();

function getShapeVertices(aw, ah, w, h) {
  const p0 = new THREE.Vector3(0, 0, 0);
  const p1 = new THREE.Vector3(-0.5 * aw, ah, 0);
  const p2 = new THREE.Vector3(-0.5 * w, ah, 0);
  const p3 = new THREE.Vector3(-0.5 * w, ah + h, 0);
  const p4 = new THREE.Vector3(0.5 * w, ah + h, 0);
  const p5 = new THREE.Vector3(0.5 * w, ah, 0);
  const p6 = new THREE.Vector3(0.5 * aw, ah, 0);
  return [p0, p1, p2, p3, p4, p5, p6];
}

const defaultTxtOption = {
  txtWidth: 9,
  txtHeight: 1,
  arrowWidth: 0.4,
  arrowHeight: 0.3,
  padding: 0.1,
  fontSize: 0.5,
  // true表示需要显示气泡的边框
  bgOutline: true,
};

export default class SimpleMarkEntity extends THREE.Group {
  /**
   * 文本内容
   * @type {string}
   */
  text = '';
  _bgMesh;
  _bgOutline;
  _txtMesh;

  // true 标签宽度和高度适应文本的宽度和高度
  _resizeByText = true;
  /**
   * @type {boolean} true 表示始终朝向相机
   * @private
   */
  _alwaysFacingEye = true;
  /**
   * @type {boolean} true 表示相对于相机，始终保持大小不变
   * @private
   */
  _keepSize = true;
  /**
   * @type {THREE.Camera}
   */
  _eye;
  /**
   * @see defaultTxtOption
   */
  _opt;

  static getTxtOption() {
    return { ...defaultTxtOption };
  }

  constructor(text, option = defaultTxtOption) {
    super();
    this._opt = option;
    const w = option.txtWidth + option.padding * 2;
    const h = option.txtHeight + option.padding * 2;
    const aw = option.arrowWidth;
    const ah = option.arrowHeight;
    const vertices = getShapeVertices(aw, ah, w, h);
    const bgGeometry = new THREE.BufferGeometry();
    bgGeometry.setAttribute(
      'position',
      vector3sToFloat32BufferAttribute(vertices),
    );
    bgGeometry.setIndex([1, 3, 2, 1, 4, 3, 1, 6, 4, 6, 5, 4, 0, 6, 1]);
    const outlineGeometry = new THREE.BufferGeometry();
    outlineGeometry.setAttribute(
      'position',
      vector3sToFloat32BufferAttribute([...vertices, vertices[0]]),
    );
    const bgMtl = new THREE.MeshBasicMaterial({
      color: '#000',
      transparent: true,
      opacity: 0.5,
      depthTest: false,
    });
    const lineMtl = new THREE.MeshBasicMaterial({
      color: '#004ddd',
      transparent: true,
      depthTest: false,
    });
    const bgMesh = new THREE.Mesh(bgGeometry, bgMtl);
    this.add(bgMesh);
    if (option.bgOutline) {
      const bgOutline = new THREE.Line(outlineGeometry, lineMtl);
      this.add(bgOutline);
      this._bgOutline = bgOutline;
    }

    const txtMesh = new Text();
    txtMesh.maxWidth = option.txtWidth;
    txtMesh.overflowWrap = 'break-word';
    txtMesh.font = assets3dBaseUrl + 'fonts/NanumGothic_Regular.ttf';
    txtMesh.fontSize = option.fontSize;
    const p3 = vertices[3];
    txtMesh.position.set(p3.x + option.padding, p3.y - option.padding, 0.01);
    txtMesh.depthOffset = -1;
    txtMesh.color = 0xffffff;
    this.add(txtMesh);
    this._txtMesh = txtMesh;
    txtMesh.material.depthTest = false;

    this._bgMesh = bgMesh;

    this.rotation.x = Math.PI * 0.5;
    this.text = text;
    this._bgMesh.geometry.computeBoundingSphere();
    this._bgMesh.geometry.computeBoundingBox();
    this.updateTextAndSize();
  }
  getFontSize() {
    return this._txtMesh.fontSize;
  }
  updateFontSize(fontSize) {
    this._txtMesh.fontSize = fontSize;
  }
  updateSize(w, h) {
    const aw = this._opt.arrowWidth;
    const ah = this._opt.arrowHeight;
    const arr = getShapeVertices(aw, ah, w, h);
    const posAttr = this._bgMesh.geometry.getAttribute('position');
    posAttr.copyArray(flatten(arr));
    posAttr.needsUpdate = true;
    const arrLine = arr.slice();
    arrLine.push(arr[0]);
    if (this._opt.bgOutline) {
      const posAttrLine = this._bgOutline.geometry.getAttribute('position');
      posAttrLine.copyArray(flatten(arrLine));
      posAttrLine.needsUpdate = true;
    }
    const p3 = arr[3];
    this._txtMesh.position.set(
      p3.x + this._opt.padding,
      p3.y - this._opt.padding,
      0.01,
    );
    this._bgMesh.geometry.computeBoundingSphere();
    this._bgMesh.geometry.computeBoundingBox();
  }

  updateTextAndSize() {
    this._txtMesh.text = this.text;
    if (this._resizeByText) {
      const _this = this;
      this._txtMesh.sync(() => {
        // const box3 = new THREE.Box3().setFromObject(txtMesh);
        const box3 = _this._txtMesh.geometry.boundingBox;
        const size = box3.getSize(new THREE.Vector3());
        const w = size.x + _this._opt.padding * 2;
        const h = size.y + _this._opt.padding * 2;
        _this.updateSize(w, h);
      });
    }
  }

  setEye(eye) {
    this._eye = eye;
    return this;
  }

  updateMatrixWorld(force) {
    if (this._eye) {
      if (this._alwaysFacingEye) {
        if (this.parent) {
          _tmpMat.copy(this.parent.matrixWorld).invert();
        }
        _tmpMat.multiply(this._eye.matrix);
        this.quaternion.setFromRotationMatrix(_tmpMat);
      }
      if (this._keepSize) {
        let factor;
        if (this._eye.isOrthographicCamera) {
          factor = (this._eye.top - this._eye.bottom) / this._eye.zoom;
        } else {
          _modelViewMatrix.multiplyMatrices(
            this._eye.matrixWorldInverse,
            this.matrixWorld,
          );
          _mvPosition.setFromMatrixPosition(_modelViewMatrix);
          factor = (-_mvPosition.z * this._eye.fov) / 120;
        }
        this.scale.set(1, 1, 1).multiplyScalar(factor * 0.12);
      }
    }
    super.updateMatrixWorld(force);
  }

  cloneMark() {
    const mark = new SimpleMarkEntity(this.text, this._opt).setEye(this._eye);
    mark.position.copy(this.position);
    return mark;
  }
}
